/*
 *
 * Copyright 2015 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_H
#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_H

#include "src/core/ext/filters/client_channel/subchannel.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/iomgr/polling_entity.h"
#include "src/core/lib/transport/connectivity_state.h"

/** A load balancing policy: specified by a vtable and a struct (which
    is expected to be extended to contain some parameters) */
typedef struct grpc_lb_policy grpc_lb_policy;
typedef struct grpc_lb_policy_vtable grpc_lb_policy_vtable;
typedef struct grpc_lb_policy_args grpc_lb_policy_args;

extern grpc_core::DebugOnlyTraceFlag grpc_trace_lb_policy_refcount;

struct grpc_lb_policy {
  const grpc_lb_policy_vtable* vtable;
  gpr_refcount refs;
  /* owned pointer to interested parties in load balancing decisions */
  grpc_pollset_set* interested_parties;
  /* combiner under which lb_policy actions take place */
  grpc_combiner* combiner;
  /* callback to force a re-resolution */
  grpc_closure* request_reresolution;
};

/// State used for an LB pick.
typedef struct grpc_lb_policy_pick_state {
  /// Initial metadata associated with the picking call.
  grpc_metadata_batch* initial_metadata;
  /// Bitmask used for selective cancelling. See \a
  /// grpc_lb_policy_cancel_picks() and \a GRPC_INITIAL_METADATA_* in
  /// grpc_types.h.
  uint32_t initial_metadata_flags;
  /// Storage for LB token in \a initial_metadata, or NULL if not used.
  grpc_linked_mdelem lb_token_mdelem_storage;
  /// Closure to run when pick is complete, if not completed synchronously.
  grpc_closure* on_complete;
  /// Will be set to the selected subchannel, or nullptr on failure or when
  /// the LB policy decides to drop the call.
  grpc_core::RefCountedPtr<grpc_core::ConnectedSubchannel> connected_subchannel;
  /// Will be populated with context to pass to the subchannel call, if needed.
  grpc_call_context_element subchannel_call_context[GRPC_CONTEXT_COUNT];
  /// Upon success, \a *user_data will be set to whatever opaque information
  /// may need to be propagated from the LB policy, or NULL if not needed.
  void** user_data;
  /// Next pointer.  For internal use by LB policy.
  struct grpc_lb_policy_pick_state* next;
} grpc_lb_policy_pick_state;

struct grpc_lb_policy_vtable {
  void (*destroy)(grpc_lb_policy* policy);

  /// \see grpc_lb_policy_shutdown_locked().
  void (*shutdown_locked)(grpc_lb_policy* policy, grpc_lb_policy* new_policy);

  /** \see grpc_lb_policy_pick */
  int (*pick_locked)(grpc_lb_policy* policy, grpc_lb_policy_pick_state* pick);

  /** \see grpc_lb_policy_cancel_pick */
  void (*cancel_pick_locked)(grpc_lb_policy* policy,
                             grpc_lb_policy_pick_state* pick,
                             grpc_error* error);

  /** \see grpc_lb_policy_cancel_picks */
  void (*cancel_picks_locked)(grpc_lb_policy* policy,
                              uint32_t initial_metadata_flags_mask,
                              uint32_t initial_metadata_flags_eq,
                              grpc_error* error);

  /** \see grpc_lb_policy_ping_one */
  void (*ping_one_locked)(grpc_lb_policy* policy, grpc_closure* on_initiate,
                          grpc_closure* on_ack);

  /** Try to enter a READY connectivity state */
  void (*exit_idle_locked)(grpc_lb_policy* policy);

  /** check the current connectivity of the lb_policy */
  grpc_connectivity_state (*check_connectivity_locked)(
      grpc_lb_policy* policy, grpc_error** connectivity_error);

  /** call notify when the connectivity state of a channel changes from *state.
      Updates *state with the new state of the policy. Calling with a NULL \a
      state cancels the subscription.  */
  void (*notify_on_state_change_locked)(grpc_lb_policy* policy,
                                        grpc_connectivity_state* state,
                                        grpc_closure* closure);

  void (*update_locked)(grpc_lb_policy* policy,
                        const grpc_lb_policy_args* args);

  /** \see grpc_lb_policy_set_reresolve_closure */
  void (*set_reresolve_closure_locked)(grpc_lb_policy* policy,
                                       grpc_closure* request_reresolution);
};

#ifndef NDEBUG
#define GRPC_LB_POLICY_REF(p, r) \
  grpc_lb_policy_ref((p), __FILE__, __LINE__, (r))
#define GRPC_LB_POLICY_UNREF(p, r) \
  grpc_lb_policy_unref((p), __FILE__, __LINE__, (r))
void grpc_lb_policy_ref(grpc_lb_policy* policy, const char* file, int line,
                        const char* reason);
void grpc_lb_policy_unref(grpc_lb_policy* policy, const char* file, int line,
                          const char* reason);
#else  // !NDEBUG
#define GRPC_LB_POLICY_REF(p, r) grpc_lb_policy_ref((p))
#define GRPC_LB_POLICY_UNREF(p, r) grpc_lb_policy_unref((p))
void grpc_lb_policy_ref(grpc_lb_policy* policy);
void grpc_lb_policy_unref(grpc_lb_policy* policy);
#endif

/** called by concrete implementations to initialize the base struct */
void grpc_lb_policy_init(grpc_lb_policy* policy,
                         const grpc_lb_policy_vtable* vtable,
                         grpc_combiner* combiner);

/// Shuts down \a policy.
/// If \a new_policy is non-null, any pending picks will be restarted
/// on that policy; otherwise, they will be failed.
void grpc_lb_policy_shutdown_locked(grpc_lb_policy* policy,
                                    grpc_lb_policy* new_policy);

/** Finds an appropriate subchannel for a call, based on data in \a pick.
    \a pick must remain alive until the pick is complete.

    If the pick succeeds and a result is known immediately, a non-zero
    value will be returned.  Otherwise, \a pick->on_complete will be invoked
    once the pick is complete with its error argument set to indicate
    success or failure.

    Any IO should be done under the \a interested_parties \a grpc_pollset_set
    in the \a grpc_lb_policy struct. */
int grpc_lb_policy_pick_locked(grpc_lb_policy* policy,
                               grpc_lb_policy_pick_state* pick);

/** Perform a connected subchannel ping (see \a
   grpc_core::ConnectedSubchannel::Ping)
    against one of the connected subchannels managed by \a policy. */
void grpc_lb_policy_ping_one_locked(grpc_lb_policy* policy,
                                    grpc_closure* on_initiate,
                                    grpc_closure* on_ack);

/** Cancel picks for \a pick.
    The \a on_complete callback of the pending picks will be invoked with \a
    *target set to NULL. */
void grpc_lb_policy_cancel_pick_locked(grpc_lb_policy* policy,
                                       grpc_lb_policy_pick_state* pick,
                                       grpc_error* error);

/** Cancel all pending picks for which their \a initial_metadata_flags (as given
    in the call to \a grpc_lb_policy_pick) matches \a initial_metadata_flags_eq
    when AND'd with \a initial_metadata_flags_mask */
void grpc_lb_policy_cancel_picks_locked(grpc_lb_policy* policy,
                                        uint32_t initial_metadata_flags_mask,
                                        uint32_t initial_metadata_flags_eq,
                                        grpc_error* error);

/** Try to enter a READY connectivity state */
void grpc_lb_policy_exit_idle_locked(grpc_lb_policy* policy);

/* Call notify when the connectivity state of a channel changes from \a *state.
 * Updates \a *state with the new state of the policy */
void grpc_lb_policy_notify_on_state_change_locked(
    grpc_lb_policy* policy, grpc_connectivity_state* state,
    grpc_closure* closure);

grpc_connectivity_state grpc_lb_policy_check_connectivity_locked(
    grpc_lb_policy* policy, grpc_error** connectivity_error);

/** Update \a policy with \a lb_policy_args. */
void grpc_lb_policy_update_locked(grpc_lb_policy* policy,
                                  const grpc_lb_policy_args* lb_policy_args);

/** Set the re-resolution closure to \a request_reresolution. */
void grpc_lb_policy_set_reresolve_closure_locked(
    grpc_lb_policy* policy, grpc_closure* request_reresolution);

/** Try to request a re-resolution. It's NOT a public API; it's only for use by
    the LB policy implementations. */
void grpc_lb_policy_try_reresolve(grpc_lb_policy* policy,
                                  grpc_core::TraceFlag* grpc_lb_trace,
                                  grpc_error* error);

#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_H */
